### NAS 监控应用完整安装部署指南

本指南将提供在基于 Debian 的飞牛 NAS 上部署和运行 Python Flask 监控应用的完整步骤，包括代码、依赖安装、文件编辑、防火墙配置以及开机自启动设置。

#### 1. 检查并安装系统依赖

在您的 NAS 上，首先确保所有必要的工具都已安装：

1. **连接到您的 NAS：**  使用 FinalShell 连接到您的飞牛 NAS 服务器，并以 `root` 用户身份登录。
2. **更新软件包列表：**

    ```
    sudo apt update

    ```
3. **检查并安装 Python 3 和** **​`venv`​** **模块：** Python 3 通常已预装，但 `venv` 模块有时需要单独安装。

    ```
    python3 --version # 检查Python 3版本
    sudo apt install python3-venv # 安装venv模块（如果提示已安装则跳过）

    ```
4. **检查并安装** **​`ufw`​** **防火墙：** 如果之前您运行 `ufw` 命令时提示 `command not found`，则需要安装它。

    ```
    ufw status # 检查ufw状态，如果command not found则需要安装
    sudo apt install ufw # 安装ufw（如果提示已安装则跳过）

    ```

#### 2. 创建并编辑 `my_monitor.py` 文件

现在，我们将在 NAS 上直接创建并编辑您的监控脚本，确保它包含所有最新的优化。

1. **导航到目标目录：**  我们建议将文件放在 `/root/` 目录。

    ```
    cd /root/

    ```
2. **使用** **​`nano`​** **编辑器创建并打开** **​`my_monitor.py`​** **文件：**

    ```
    nano my_monitor.py

    ```
3. **复制并粘贴代码：** 将下方提供的**优化版** **​`my_monitor.py`​** **代码**的全部内容复制到剪贴板，然后粘贴到 `nano` 编辑器中（通常是鼠标右键点击）。

    * **确保清除** **​`nano`​** **中所有旧内容**（按住 `Ctrl` 然后反复按 `K`）。
    * 粘贴后，快速检查代码格式，确保没有乱码或异常换行。

    ```
    import os
    import psutil
    import time
    import threading
    from flask import Flask, jsonify

    app = Flask(__name__)

    # --- 可配置项 ---
    # 物理网卡名称的常见前缀。您的NAS网卡名称通常会以这些前缀开头。
    # Common prefixes for physical network interface names. Your NAS NICs usually start with these prefixes.
    # 示例：'eth', 'enp', 'wlan'
    # Examples: 'eth', 'enp', 'wlan'
    PHYSICAL_NIC_PREFIXES = ["eth", "enp", "wlan"] # 请根据您NAS的实际网卡命名调整

    # 全局变量存储最新网速和锁，用于确保多线程环境下对共享数据的安全访问
    # Global variable to store the latest network speed and a lock
    # to ensure safe access to shared data in a multi-threaded environment.
    current_speed = {"dSpeed": 0.0, "uSpeed": 0.0}
    speed_lock = threading.Lock() # 用于保护 current_speed 的锁 # Lock to protect current_speed

    # 获取 CPU 使用率
    # Get CPU usage percentage
    def get_cpu_usage():
        # interval=1 表示在过去1秒内的CPU平均使用率
        # interval=1 means the average CPU usage over the past 1 second.
        return psutil.cpu_percent(interval=1)

    # 获取内存使用情况百分比
    # Get memory usage percentage
    def get_memory_usage():
        # psutil.virtual_memory().percent 直接返回内存使用率百分比，无需手动计算
        # psutil.virtual_memory().percent directly returns the memory usage percentage,
        # no manual calculation needed.
        return psutil.virtual_memory().percent

    # 获取 CPU 温度
    # Get CPU temperature
    def get_cpu_temperature():
        try:
            # 使用 psutil.sensors_temperatures() 获取所有传感器数据
            # Use psutil.sensors_temperatures() to get all sensor data
            temps = psutil.sensors_temperatures()

            # 遍历所有传感器类型
            # Iterate through all sensor types
            for sensor_name, sensor_list in temps.items():
                # 查找 coretemp 适配器下的 Package id 0 或类似的 CPU 封装温度
                # Look for Package id 0 or similar CPU package temperature under coretemp adapter
                if "coretemp" in sensor_name.lower():
                    for entry in sensor_list:
                        # 'label' 属性通常是 'Package id 0' 或 'Core X'
                        # 'label' attribute is usually 'Package id 0' or 'Core X'
                        if "package id 0" in entry.label.lower():
                            return entry.current # 返回当前温度值 # Return current temperature value

            # 如果 coretemp 或 Package id 0 未找到，尝试回退到 thermal_zone0
            # If coretemp or Package id 0 is not found, try falling back to thermal_zone0
            with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
                temp = f.read().strip()
            return float(temp) / 1000  # 温度单位转换为摄氏度 # Convert temperature unit to Celsius

        except FileNotFoundError:
            print("Warning: CPU temperature file /sys/class/thermal/thermal_zone0/temp not found. This feature might not be supported on this system.")
            return None
        except Exception as e:
            print(f"Error getting CPU temperature: {e}")
            return None

    # 后台线程：每秒更新网速
    # Background thread: Update network speed every second
    def update_network_speed():
        """后台线程：每秒更新网速"""
        global current_speed, speed_lock
        while True:
            # 获取所有网卡的I/O计数器 (pernic=True 会返回一个字典，键是网卡名称)
            # Get I/O counters for all network interfaces (pernic=True returns a dictionary where keys are NIC names)
            net_io_all_start = psutil.net_io_counters(pernic=True)

            prev_rx = 0
            prev_tx = 0
            # 汇总所有物理网卡的初始接收和发送字节数
            # Sum initial received and sent bytes for all physical NICs
            for nic_name, stats in net_io_all_start.items():
                # 检查网卡名称是否以物理网卡前缀开头且不是回环接口
                # Check if NIC name starts with a physical NIC prefix and is not the loopback interface
                if any(nic_name.startswith(prefix) for prefix in PHYSICAL_NIC_PREFIXES) and nic_name != "lo":
                    prev_rx += stats.bytes_recv
                    prev_tx += stats.bytes_sent

            start_time = time.time()

            # 等待1秒
            # Wait for 1 second
            time.sleep(1)

            # 再次获取所有网卡的I/O计数器
            # Get I/O counters for all network interfaces again
            net_io_all_end = psutil.net_io_counters(pernic=True)

            current_rx = 0
            current_tx = 0
            # 汇总所有物理网卡的最终接收和发送字节数
            # Sum final received and sent bytes for all physical NICs
            for nic_name, stats in net_io_all_end.items():
                if any(nic_name.startswith(prefix) for prefix in PHYSICAL_NIC_PREFIXES) and nic_name != "lo":
                    current_rx += stats.bytes_recv
                    current_tx += stats.bytes_sent

            elapsed = time.time() - start_time # 实际经过的时间 # Actual elapsed time

            # 计算下载和上传速度 (Bytes/second)
            download_speed_bps = (current_rx - prev_rx) / elapsed
            upload_speed_bps = (current_tx - prev_tx) / elapsed

            # 这里不再除以1024，直接返回 Bytes/second，以匹配小电视可能期望的单位
            # No longer dividing by 1024 here, directly returning Bytes/second,
            # to match the unit the small TV might be expecting.
            d_speed_bytes_per_sec = round(download_speed_bps, 2)
            u_speed_bytes_per_sec = round(upload_speed_bps, 2)

            # 使用锁来安全地更新全局变量
            # Use a lock to safely update the global variable
            with speed_lock:
                current_speed["dSpeed"] = d_speed_bytes_per_sec # 现在是 Bytes/s # Now Bytes/s
                current_speed["uSpeed"] = u_speed_bytes_per_sec # 现在是 Bytes/s # Now Bytes/s

    # 启动后台线程，设置为守护线程，确保主程序退出时它也退出
    # Start the background thread, set as daemon to ensure it exits when the main program exits.
    network_thread = threading.Thread(target=update_network_speed, daemon=True)
    network_thread.start()

    @app.route("/system_stats", methods=["GET"])
    def system_stats():
        # 获取系统信息
        # Get system information
        with speed_lock:
            # 在读取全局变量时也要使用锁
            # Use the lock when reading the global variable as well
            stats = {
                "cpuPer": get_cpu_usage(),
                "cpuTemp": get_cpu_temperature(),
                "ramPer": get_memory_usage(),
                "dSpeed": current_speed["dSpeed"], # 现在是 Bytes/s # Now Bytes/s
                "uSpeed": current_speed["uSpeed"]  # 现在是 Bytes/s # Now Bytes/s
            }

        # 返回 JSON 格式的数据
        # Return data in JSON format
        return jsonify(stats)

    if __name__ == "__main__":
        # 在所有网络接口上监听 5000 端口
        # Listen on port 5000 on all network interfaces
        # 注意：debug=True 仅用于开发环境，生产环境应设置为 False
        # Note: debug=True is for development environment only, should be False in production.
        app.run(host="0.0.0.0", port=5000, debug=True)

    ```
4. **调整** **​`PHYSICAL_NIC_PREFIXES`​**  **(可选但推荐)：** 在您粘贴的代码顶部，找到 `PHYSICAL_NIC_PREFIXES = ["eth", "enp", "wlan"]` 这一行。

    * 如果您清楚 NAS 物理网卡的命名规则（例如 `eth0`, `eth1`, `enp1s0`），可以保留这个列表。
    * 如果您的网卡名称有其他前缀，请添加到这个列表中。这有助于确保只统计物理网口的流量。
5. **保存并退出** **​`nano`​**​ **：**

    * 按下 `Ctrl + O` 保存文件（会提示文件名，直接回车确认）。
    * 按下 `Ctrl + X` 退出编辑器。

#### 3. 验证文件内容

为了确保代码已正确保存到文件中，您可以再次使用 `cat` 命令查看文件内容。

```
cat my_monitor.py

```

检查输出，确认代码内容和格式是完整的，没有遗漏或乱码。

#### 4. 设置 Python 虚拟环境并安装依赖

为了避免与系统 Python 包冲突，强烈建议为您的 Flask 应用创建一个独立的 Python 虚拟环境。

1. **创建虚拟环境：** 在 `/root/` 目录下执行：

    ```
    python3 -m venv venv

    ```
2. **激活虚拟环境：**

    ```
    source venv/bin/activate

    ```

    激活成功后，您的命令行提示符通常会在前面显示 `(venv)`。
3. **安装 Flask 和 psutil 库：** 在虚拟环境已激活的状态下，使用 `pip` 安装依赖。

    ```
    pip install flask psutil

    ```

#### 5. 配置防火墙（非常关键！）

如果您的 NAS 从外部无法访问（例如小电视无法获取数据），最常见的原因是防火墙阻止了连接。您需要确保 5000 端口已开放。

1. **允许 5000 端口并启用** **​`ufw`​**​ **：**

    ```
    sudo ufw allow 5000/tcp   # 允许TCP协议的5000端口
    sudo ufw enable           # 启用ufw防火墙。如果ufw是禁用状态，会提示是否启用，Y确认
    sudo ufw status           # 检查ufw状态，确认5000端口已允许

    ```

    * **云服务器用户注意：**  如果您的 NAS 是云服务商提供的（例如阿里云、腾讯云等），您还需要在云服务商的控制台中检查**安全组 (Security Group)**  或**网络ACL (Network Access Control List)**  规则，确保允许来自您本地电脑 IP 地址（或所有 IP 地址 `0.0.0.0/0`）的 5000 端口 TCP 连接。

#### 6. 配置 Systemd 实现开机自启动

将 Flask 应用配置为 Systemd 服务，使其能在 NAS 开机时自动启动并在后台运行。

1. **创建服务文件：** 打开或创建 `my_monitor.service` 文件：

    ```
    sudo nano /etc/systemd/system/my_monitor.service

    ```

    将以下内容粘贴到文件中，然后保存并退出（`Ctrl+O`，回车，`Ctrl+X`）。

    ```
    [Unit]
    Description=My NAS Monitor Flask App
    After=network.target

    [Service]
    User=root
    WorkingDirectory=/root/
    ExecStart=/root/venv/bin/python /root/my_monitor.py
    Restart=always

    [Install]
    WantedBy=multi-user.target

    ```

    * **注意：**  确保 `ExecStart` 中的 Python 解释器路径 `/root/venv/bin/python` 和脚本路径 `/root/my_monitor.py` 是您实际存放文件的准确路径。
2. **重新加载 Systemd 配置：**

    ```
    sudo systemctl daemon-reload

    ```
3. **启用服务（设置开机自启动）：**

    ```
    sudo systemctl enable my_monitor.service

    ```
4. **启动服务（立即运行）：**

    ```
    sudo systemctl start my_monitor.service

    ```
5. **检查服务状态：** 确认服务是否正在运行：

    ```
    sudo systemctl status my_monitor.service

    ```

    您应该看到 `Active: active (running)`。

#### 7. 测试和验证

1. **从本地电脑访问 API：** 在您的本地电脑浏览器中，输入 NAS 的 IP 地址和 API 路径：

    ```
    http://192.168.3.46:5000/system_stats

    ```

    （请将 `192.168.3.46` 替换为您 NAS 的实际 IP 地址）。
2. **预期结果：** 浏览器将显示一个 JSON 格式的系统监控数据。这次 `dSpeed` 和 `uSpeed` 的数值应该会更大（因为它们现在是 Bytes/second），并且应该能够更准确地在您的小电视上显示。

```
{
  "cpuPer": 7.3,
  "cpuTemp": 43.0,
  "dSpeed": 10908830.00, # 预计会是这个数量级，因为现在是 Bytes/s
  "ramPer": 13.6,
  "uSpeed": 154190.00   # 预计会是这个数量级，因为现在是 Bytes/s
}

```